# cutting_pass.py

r'''cutting passes

sequence(*rests)
  Runs a sequence of cutting_passes.
cut(rest)
  Generates one *cut*, rest does the cutting.  Brackets the cut with
  dest_machine.start_cut/end_cut.
gen_pass(rest, rapid_fun)
  Generates one *pass*, rest does the cutting.
update_params(rest, new_params)
  Updates params with new_params.
step(rest, var, inc, *points)
  Steps var through a range of values.
generate()
  generates one point.

Terminology:

* A *pass* is a continuous cut that does not lift the tool from the work.
  Passes are generally separated by rapid movements to position the tool prior
  to each pass.
* A *cut* is a series of passes cut at the same cutting depth.  For example
  a series of roughing *cuts* is done prior to a finishing *cut*.  G-code line
  numbers (Nnnnn) are output for each cut.
'''

import sys    # for debugging

from ot_simulator.template import param_funs
from ot_simulator.template.template_funs import frange
from ot_simulator.cutting_pass import output_wrapper

class PauseException(Exception):
    pass

class sequence(object):
    def __init__(self, *steps):
        self.steps = tuple(steps)
        self.index = 0

    def run(self, rest, setup, dest_machine, params, index=0, rest_fn='run'):
        for i in xrange(index, len(self.steps)):
            method = getattr(self.steps[i], rest_fn)
            method(rest, setup, dest_machine, params)
            self.index = i + 1
            rest_fn = 'run'
        self.index = 0

    def cont(self, rest, setup, dest_machine, params, index=0, rest_fn='run'):
        self.run(rest, setup, dest_machine, params, self.index, 'cont')

class generate(object):
    r'''Generates one point.

    This must be the last cutting_pass.
    '''
    def __init__(self):
        pass

    def run(self, rest, setup, dest_machine, params):
        #print >> sys.stderr, "generate: params"
        #for key in sorted(params.iterkeys()):
        #    print >> sys.stderr, "  ", key, params[key]
        if rest:
            raise AssertionError("generate: not last cutting_pass")
        if 'start_pass' in params:
            params['start_pass'](setup, dest_machine, params)
        point = setup.generate_point(params)
        dest_machine.output_point(point, params)

    def cont(self, rest, setup, dest_machine, params):
        self.run(rest, setup, dest_machine, params)

class step(object):
    r'''Steps ``var`` through ``points`` incrementing by ``inc``.

    Runs ``rest`` for each value generated.

    ``inc`` and each point in ``points`` must be a :mod:`.param_fun`.
    '''
    def __init__(self, var, inc, *points):
        self.var = var
        self.inc = inc
        self.points = tuple(points)

    def run(self, rest, setup, dest_machine, params,
            points = None, rest_fn='run'):
        if points is None:
            points = tuple(param_funs.get_value(p, params) for p in self.points)
        try:
            for x in frange(param_funs.get_value(self.inc, params), *points):
                p2 = params.copy()
                #print >> sys.stderr, "step: setting", self.var, "to", x
                p2[self.var] = x
                method = getattr(rest[0], rest_fn)
                method(rest[1:], setup, dest_machine, p2)
                rest_fn = 'run'
        except PauseException:
            self.last_x = x

    def cont(self, rest, setup, dest_machine, params):
        inc = param_funs.get_value(self.inc, params)
        points = tuple(param_funs.get_value(p, params) for p in self.points)
        last_x = self.last_x
        del self.last_x
        def past_last_x(x):
            if inc < 0: return x < last_x
            return x > last_x
        for i, x in enumerate(points):
            if past_last_x(x):
                if i == 0:
                    self.run(rest, setup, dest_machine, params, points, 'cont')
                else:
                    prev_point = points[i - 1]
                    if past_last_x(prev_point + inc):
                        self.run(rest, setup, dest_machine, params,
                                 (prev_point + inc,) + points[i:],
                                 'cont')
                    else:
                        self.run(rest, setup, dest_machine, params, points[i:],
                                 'cont')

class gen_pass(object):
    def __init__(self, rapid_fun):
        self.rapid_fun = rapid_fun

    def run(self, rest, setup, dest_machine, params, state=0, rest_fn='run'):
        self.state = state
        self.rest_fn = rest_fn
        if self.state < 4:
            params['start_pass'] = self.start_pass
            method = getattr(rest[0], rest_fn)
            method(rest[1:], setup, dest_machine, params)
            self.state = 4
        if self.state == 4:
            dest_machine.end_pass(params)
            self.state = 0
            self.rest_fn = 'run'

    def cont(self, rest, setup, dest_machine, params):
        self.run(rest, setup, dest_machine, params, self.state, 'cont')

    def start_pass(self, setup, dest_machine, params):
        if self.state == 0:
            dest_machine.start_pass(params)
            self.state = 1
        if self.state == 1:
            point = setup.generate_point(params)
            self.state = 2
        if self.state == 2:
            self.rapid_fun(point, params, dest_machine)
            self.state = 3

class cut(object):
    def __init__(self):
        self.last_lineno = None

    def run(self, rest, setup, dest_machine, params):
        #print >> sys.stderr, "cut: start_cut"
        lineno = params.get('lineno')
        if lineno is not None:
            if lineno == self.last_lineno:
                lineno += self.line_inc
                params['lineno'] = lineno
                self.line_inc += 1
            else:
                self.last_lineno = lineno
                self.line_inc = 1
        dest_machine.start_cut(params, lineno)
        rest[0].run(rest[1:], setup, dest_machine, params)
        #print >> sys.stderr, "cut: end_cut"
        dest_machine.end_cut(params)

    def cont(self, rest, setup, dest_machine, params):
        #print >> sys.stderr, "cut.cont"
        rest[0].cont(rest[1:], setup, dest_machine, params)
        #print >> sys.stderr, "cut.cont: end_cut"
        dest_machine.end_cut(params)

class update_params(object):
    def __init__(self, **new_params):
        self.new_params = new_params

    def run(self, rest, setup, dest_machine, params):
        p2 = params.copy()
        p2.update(self.new_params)
        rest[0].run(rest[1:], setup, dest_machine, p2)

    def cont(self, rest, setup, dest_machine, params):
        p2 = params.copy()
        p2.update(self.new_params)
        rest[0].cont(rest[1:], setup, dest_machine, p2)

class add_offset_fn(object):
    def __init__(self, offset_fn):
        self.offset_fn = offset_fn

    def run(self, rest, setup, dest_machine, params):
        new_dest_machine = \
          output_wrapper.offset_dest(dest_machine, self.offset_fn)
        rest[0].run(rest[1:], setup, new_dest_machine, params)

    def cont(self, rest, setup, dest_machine, params):
        new_dest_machine = \
          output_wrapper.offset_dest(dest_machine, self.offset_fn)
        rest[0].cont(rest[1:], setup, new_dest_machine, params)

class add_debuggers(object):
    def __init__(self, *debuggers):
        self.debuggers = debuggers

    def run(self, rest, setup, dest_machine, params):
        if self.debuggers:
            new_dest_machine = \
              output_wrapper.debug_dest(dest_machine, *self.debuggers)
        else:
            new_dest_machine = dest_machine
        rest[0].run(rest[1:], setup, new_dest_machine, params)

    def cont(self, rest, setup, dest_machine, params):
        if self.debuggers:
            new_dest_machine = \
              output_wrapper.debug_dest(dest_machine, *self.debuggers)
        else:
            new_dest_machine = dest_machine
        rest[0].cont(rest[1:], setup, new_dest_machine, params)

